<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE HTML>
<html>
<head>
  <title>opens additional content that should be converted to https</title>
  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
  <link rel="stylesheet" type="text/css"
        href="chrome://mochikit/content/tests/SimpleTest/test.css"/>

  <script class="testbody" type="text/javascript">
  "use strict";

  // We define |content| as a global as a hack to prevent use of |content|
  // inside a ContentTask tripping ESLint no-undef checks.
  /* global content */

  SimpleTest.waitForExplicitFinish();

  const Cc = Components.classes;
  const Ci = Components.interfaces;
  const Cu = Components.utils;
  const STSPATH = "/tests/security/manager/ssl/tests/mochitest/stricttransportsecurity";
  const NUM_TEST_FRAMES = 4;
  const CONTENT_PAGE =
    "http://mochi.test:8888/chrome/security/manager/ssl/tests/mochitest/stricttransportsecurity/page_blank.html";

  Cu.import("resource://testing-common/BrowserTestUtils.jsm");
  Cu.import("resource://testing-common/ContentTask.jsm");
  Cu.import("resource://gre/modules/Task.jsm");

  // This is how many sub-tests (testframes) in each round.
  // When the round begins, this will be initialized.
  var testsleftinround = 0;
  var currentround = "";
  var mainWindow =
    window.QueryInterface(Ci.nsIInterfaceRequestor).
    getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShellTreeItem).
    rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor).
    getInterface(Ci.nsIDOMWindow);

  SpecialPowers.Services.prefs.setIntPref("browser.startup.page", 0);

  var testframes = {
    'samedom':
      {'url':     "http://example.com" + STSPATH + "/verify.sjs",
        'expected': {'plain': 'SECURE',
                     'subdom': 'SECURE',
                     'nosts': 'INSECURE'}},
    'subdom':
      {'url':     "http://test1.example.com" + STSPATH + "/verify.sjs",
        'expected': {'plain': 'INSECURE',
                     'subdom': 'SECURE',
                     'nosts': 'INSECURE'}},
    'otherdom':
      {'url':     "http://example.org" + STSPATH + "/verify.sjs",
        'expected': {'plain': 'INSECURE',
                     'subdom': 'INSECURE',
                     'nosts': 'INSECURE'}},
    'alreadysecure':
      {'url':     "https://test2.example.com" + STSPATH + "/verify.sjs",
        'expected': {'plain': 'SECURE',
                     'subdom': 'SECURE',
                     'nosts': 'SECURE'}},
  };

  function whenDelayedStartupFinished(aWindow, aCallback) {
    SpecialPowers.Services.obs.addObserver(function observer(aSubject, aTopic) {
      if (aWindow == aSubject) {
        SpecialPowers.Services.obs.removeObserver(observer, aTopic);
        SimpleTest.executeSoon(aCallback);
      }
    }, "browser-delayed-startup-finished", false);
  }

  function testOnWindow(aIsPrivate, aCallback) {
    let win = mainWindow.OpenBrowserWindow({private: aIsPrivate});

    Task.spawn(function* () {
      yield new Promise(resolve => whenDelayedStartupFinished(win, resolve));

      let browser = win.gBrowser.selectedBrowser;
      yield BrowserTestUtils.loadURI(browser, CONTENT_PAGE);
      yield BrowserTestUtils.browserLoaded(browser);

      aCallback(win);
    });
  }

  function startRound(win, isPrivate, round) {
    currentround = round;
    testsleftinround = NUM_TEST_FRAMES;
    SimpleTest.info("TESTS LEFT IN ROUND " + currentround + ": " + testsleftinround);

    let browser = win.gBrowser.selectedBrowser;
    let src = "https://example.com" + STSPATH + "/" + round + "_bootstrap.html";

    ContentTask.spawn(browser, src, function* (contentSrc) {
      let frame = content.document.createElement("iframe");
      frame.setAttribute('id', 'ifr_bootstrap');
      frame.setAttribute('src', contentSrc);

      return new Promise(resolve => {
        frame.addEventListener("load", resolve);
        content.document.body.appendChild(frame);
      });
    }).then(() => {
      onMessageReceived(win, isPrivate, "BOOTSTRAP " + round);
    });
  }

  function loadVerifyFrames(win, isPrivate, round) {
    loadVerifyFrame(win, isPrivate, testframes.samedom, 'samedom', function() {
      loadVerifyFrame(win, isPrivate, testframes.subdom, 'subdom', function() {
        loadVerifyFrame(win, isPrivate, testframes.otherdom, 'otherdom', function() {
          loadVerifyFrame(win, isPrivate, testframes.alreadysecure, 'alreadysecure');
        });
      });
    });
  }

  function loadVerifyFrame(win, isPrivate, test, testName, aCallback) {
    let id = 'ifr_' + testName;
    let src = test.url + '?id=' + testName;
    let browser = win.gBrowser.selectedBrowser;

    ContentTask.spawn(browser, [id, src], function* ([contentId, contentSrc]) {
      let frame = content.document.createElement("iframe");
      frame.setAttribute('id', contentId);
      frame.setAttribute('src', contentSrc);

      return new Promise(resolve => {
        frame.addEventListener("load", () => {
          resolve(frame.contentDocument.location.protocol);
        });

        content.document.body.appendChild(frame);
      });
    }).then(scheme => {
      if (scheme == 'https:') {
        onMessageReceived(win, isPrivate, "SECURE " + testName);
      } else {
        onMessageReceived(win, isPrivate, "INSECURE " + testName);
      }

      if (aCallback) {
        aCallback();
      }
    });
  }

  /**
   * Messages received are in this format:
   *  (BOOTSTRAP|SECURE|INSECURE) testid
   * For example: "BOOTSTRAP subdom"
   *          or: "INSECURE otherdom"
   */
  function onMessageReceived(win, isPrivate, data) {
    let result = data.split(/\s+/);
    if (result.length != 2) {
      SimpleTest.ok(false, data);
      return;
    }

    if (result[0] === "BOOTSTRAP") {
      loadVerifyFrames(win, isPrivate, currentround);
      return;
    }

    // check if the result (SECURE/INSECURE) is expected for this round/test
    // combo
    dump_STSState(isPrivate);
    SimpleTest.is(result[0], testframes[result[1]].expected[currentround],
                             "in ROUND " + currentround +
                             ", test " + result[1]);
    testsleftinround--;

    // if this round is complete...
    if (testsleftinround < 1) {
      SimpleTest.info("DONE WITH ROUND " + currentround);
      // remove all the iframes in the document
      let browser = win.gBrowser.selectedBrowser;
      ContentTask.spawn(browser, testframes, function* (contentTestFrames) {
        content.document.body.removeChild(
          content.document.getElementById('ifr_bootstrap'));
        for (let test in contentTestFrames) {
          content.document.body.removeChild(
            content.document.getElementById('ifr_' + test));
        }
      }).then(() => {
        currentround = "";

        if (!isPrivate) {
          clean_up_sts_state(isPrivate);
        }
        // Close test window.
        win.close();
        // And advance to the next test.
        // Defer this so it doesn't muck with the stack too much.
        SimpleTest.executeSoon(nextTest);
      });
    }
  }

  function test_sts_before_private_mode() {
    testOnWindow(false, function(win) {
      SimpleTest.info("In public window");
      dump_STSState(false);
      startRound(win, false, 'plain');
    });
  }

  function test_sts_in_private_mode() {
    testOnWindow(true, function(win) {
      SimpleTest.info("In private window");
      dump_STSState(true);
      startRound(win, true, 'subdom');
    });
  }

  function test_sts_after_exiting_private_mode() {
    testOnWindow(false, function(win) {
      SimpleTest.info("In a new public window");
      dump_STSState(false);
      startRound(win, false, 'nosts');
    });
  }

  function clean_up_sts_state(isPrivate) {
    // erase all signs that this test ran.
    SimpleTest.info("Cleaning up STS data");
    let flags = isPrivate ? Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
    SpecialPowers.cleanUpSTSData("http://example.com", flags);
    dump_STSState(isPrivate);
  }

  function dump_STSState(isPrivate) {
    let sss = Cc["@mozilla.org/ssservice;1"]
                .getService(Ci.nsISiteSecurityService);
    let flags = isPrivate ? Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
    SimpleTest.info("State of example.com: " + sss.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS, "example.com", flags));
  }

  // These are executed in the order presented.
  // 0.  test that STS works before entering private browsing mode.
  //     (load sts-bootstrapped "plain" tests)
  //  ... clear any STS data ...
  // 1.  test that STS works in private browsing mode
  //     (load sts-bootstrapped "subdomain" tests)
  // 2.  test that after exiting private browsing, STS data is forgotten
  //     (verified with non-sts-bootstrapped pages)
  //  ... clear any STS data ...
  var tests = [
    test_sts_before_private_mode,
    test_sts_in_private_mode,
    test_sts_after_exiting_private_mode
  ];

  function finish() {
    SpecialPowers.Services.prefs.clearUserPref("browser.startup.page");
    SimpleTest.finish();
  }
  function nextTest() {
    SimpleTest.executeSoon(tests.length ? tests.shift() : finish);
  }
  window.addEventListener('load', nextTest, false);
  </script>
</head>

<body>
  This test will load some iframes and do some tests.
</body>
</html>
